/*
 * Copyright (C) 2013 Wojciech Dzierżanowski
 * See LICENSE.txt for licensing details.
 */

package wdzierzan.downstream.core;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Wojciech Dzierżanowski <wojciech.dzierzanowski@gmail.com>
 */
public class StreamerHandler {

    private static final Logger logger = Logger.getLogger(StreamerHandler.class.getName());

    private final TargetFileFactory targetFileFactory;
    private final ReceiverRunner receiverRunner;
    private Future<Object> activeReceiver;
    private boolean canceled;
    private final ProgressReport progressReport;
    private int destPort;
    private Streamer streamer;
    private String extension;
    private int predictedFileCount;
    private int currentFileCount;

    public StreamerHandler(TargetFileFactory targetFileFactory, ReceiverRunner receiverRunner, ProgressReport progressReport) {
        this.targetFileFactory = targetFileFactory;
        this.receiverRunner = receiverRunner;
        this.progressReport = progressReport;
    }

    public void stream(Streamer streamer, int destPort, String sourcePath, String destPath, String extension)
            throws IOException {
        this.streamer = streamer;
        this.destPort = destPort;
        this.extension = extension;
        this.predictedFileCount = 1;
        this.currentFileCount = 0;
        this.canceled = false;

        streamRecursive(sourcePath, destPath, 0);

        progressReport.updateProgress(predictedFileCount, currentFileCount);
    }

    public void cancel() {
        if (!canceled) {
            canceled = true;
            logger.info("Canceling");
            if (activeReceiver != null)
                activeReceiver.cancel(true);
        }
    }

    public boolean isCanceled() {
        return canceled;
    }

    private void streamRecursive(String sourcePath, String destPath, int depth) throws IOException {

        String[] sourcePaths = streamer.listFiles(sourcePath);

        File sourceFile = new File(sourcePath);
        File destFile = new File(destPath);
        // Just so we know we shouldn't be using it if we do.
        destPath = null;

        if (depth == 0 && destFile.isDirectory())
            destFile = new File(destFile, sourceFile.getName());

        if (sourcePaths != null)
            // It's a directory, and we counted it as a file.
            --predictedFileCount;

        if (sourcePaths == null || sourcePaths.length == 0) {
            if (sourcePaths == null)
                streamFile(sourcePath, destFile.getPath());
            return;
        }

        if (destFile.exists() && !destFile.isDirectory())
            throw new IOException("Cannot overwrite file with directory");

        TargetFile destDirectory = targetFileFactory.getTargetDirectory(destFile);
        // Just so we know we shouldn't be using it if we do.
        destFile = null;

        predictedFileCount += sourcePaths.length;

        try {
            for (String childPath : sourcePaths) {

                if (canceled)
                    return;

                boolean childIsDirectory = false;
                if (childPath.endsWith("/")) {
                    childIsDirectory = true;
                    childPath = childPath.substring(0, childPath.length() - 1);
                }

                String fullSourcePath = sourcePath + '/' + childPath;
                String fullDestPath = destDirectory.getPath() + '/' + childPath;

                if (childIsDirectory)
                    streamRecursive(fullSourcePath, fullDestPath, depth + 1);
                else
                    streamFile(fullSourcePath, fullDestPath);
            }
            destDirectory.commit();
        } finally {
            destDirectory.release();
        }
    }

    private void streamFile(String sourcePath, String destPath) throws IOException {
        Streamer.FileType type = streamer.identify(sourcePath);
        switch (type) {
            case UNSTREAMABLE:
                --predictedFileCount;
                return;
            case LOSSLESS_AUDIO:
                destPath = destPath + extension;
                break;
        }

        logger.log(Level.INFO, "Streaming {0} -> {1}", new Object[] { sourcePath, destPath });

        progressReport.updateProgress(predictedFileCount, currentFileCount);

        if (canceled)
            return;

        TargetFile destFile = targetFileFactory.getTargetFile(destPath);

        // Just so we know we shouldn't be using it if we do.
        destPath = null;

        activeReceiver = receiverRunner.startReceiver(destPort, destFile.getPath(), progressReport);
        try {
            streamer.stream(sourcePath, type);
            activeReceiver.get();
            destFile.commit();
        } catch (InterruptedException e) {
            throw new IOException(e.toString());
        } catch (ExecutionException e) {
            throw new IOException(e.toString());
        } finally {
            if (!canceled)
                ++currentFileCount;
            activeReceiver = null;
            destFile.release();
        }
    }
}
